﻿//////////////////////////////////////////////////////////////////
//
// Network\NetworkServer.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Network
{
   public class NetworkServer : NetworkGui
   {
      private Dictionary<string, DateTime> connectedPlanets_;

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      public NetworkServer(string senderId, GuiObjects gui)
         : base(senderId, gui, false)
      {
         connectedPlanets_ = new Dictionary<string, DateTime>(StringComparer.CurrentCultureIgnoreCase);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      public override void Shutdown()
      {
         base.Shutdown();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="message"></param>
      private bool processConnectPlanet(IConnectPlanet connectPlanet)
      {
         try
         {
            connectedPlanets_[connectPlanet.PlanetName] = connectPlanet.LastKnownGood;

            // Have we already started the planet?
            if ((null != Planet) && (connectPlanet.PlanetName == Planet.Fact.DisplayName))
            {
               announcePlanetOnline(true);
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="message"></param>
      private bool processLoadTile(ILoadTile loadTile)
      {
         try
         {
            if ((null == Planet) || (loadTile.PlanetName != Planet.Fact.DisplayName))
            {
               // we are not running this planet, ignore it quietly
               return true;
            }

            // Get the tile information from the planet
            ITerrain terrain = Planet.Terrain;
            if (null == terrain)
            {
               // We do not have any information for this tile
               return true;
            }

            // Load the tile and send it back to the caller
            short[,] heights = terrain.LoadTile(loadTile.Location);
            if (null != heights)
            {
               Program.Transport.TileImage(loadTile.PlanetName, loadTile.Location, heights);
            }

            return true;
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Abstract implementation of NetworkGui
      /// </summary>
      /// <param name="eventArgs"></param>
      /// <returns></returns>
      protected override bool HandleReceivedMessage(ReceivedMessageEventArgs eventArgs)
      {
         try
         {
            IConnectPlanet connectPlanet;
            if (null != (connectPlanet = eventArgs.message as IConnectPlanet))
            {
               // It's a planet request (from a client to a server).
               return processConnectPlanet(connectPlanet);
            }
            ILoadTile loadTile;
            if (null != (loadTile = eventArgs.message as ILoadTile))
            {
               // It's a tile request (from a client to a server).
               return processLoadTile(loadTile);
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// We are a server and want to run the planet
      /// </summary>
      public bool startPlanet(string name)
      {
         // Create the planet
         IPlanetFact planetFact = Program.Almanac.Planets
            .First(planet => planet.DisplayName == name);
         Planet = Program.Loader.Create<IPlanet>();
         if (Planet.Initialize(planetFact, name))
         {
            // Subscribe to action requests for the planet
            if (!Transport.StartPlanet(name))
            {
               return false;
            }

            // Announce that the planet is now running
            logger_.DebugFormat("server is now running planet '{0}'", PlanetName);

            // Does someone already want this planet?
            if (connectedPlanets_.ContainsKey(PlanetName))
            {
               announcePlanetOnline(true);
            }

            // And start the simulation
            Planet.Run();
            return true;
         }
         logger_.ErrorFormat("failed to run planet '{0}'", PlanetName);
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// We are a server and want stop running the planet
      /// </summary>
      public void stopPlanet()
      {
         // Tell the network that we have stopped
         Transport.StopPlanet(PlanetName);
         Planet = null;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Tell the world that the planet has started/stopped
      /// </summary>
      private void announcePlanetOnline(bool online)
      {
         IPlanetOnline planetOnline = Program.Loader.Create<IPlanetOnline>();
         planetOnline.PlanetName = Planet.Fact.DisplayName;
         planetOnline.StartLocation = Planet.Fact.HomeAddress.Clone().Move(0, 0, 50);
         planetOnline.Orientation = planetOnline.StartLocation.Clone().Move(10, 10, -10);
         planetOnline.Online = online;
         Transport.Broadcast<IPlanetOnline>(planetOnline);
      }
   }
}
